package com.ruge.core.log;

import cn.hutool.json.JSONUtil;
import com.ruge.core.exception.BaseBusinessException;
import com.ruge.core.exception.IError;
import com.ruge.core.response.Response;
import com.ruge.core.tool.FormatTool;
import com.ruge.tool.json.JsonTool;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import nl.bitwalker.useragentutils.UserAgent;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;

/**
 * TODO 好没想好怎么处理这个swagger的aop  目前的问题是获取不到requestbody的参数
 *
 * @author ruge.wu
 * @since 2021/12/8 15:53
 */
@Slf4j
//@Aspect
//@Component
public class SwaggerLogAspect {
    @Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
    public void pointcut() {
    }

    /**
     * 前置通知
     *
     * @param point {@link JoinPoint}
     */
    @Before("pointcut()")
    public void beforeLog(JoinPoint point) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        log.info("【前置通知 请求 URL】：{}", request.getRequestURL());
        log.info("【前置通知 请求 IP】：{}", request.getRemoteAddr());
        log.info("【前置通知 请求类名】：{}，【请求方法名】：{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName());
        log.info("【前置通知 body】：{}，", JSONUtil.toJsonStr(point.getArgs()));
        Map<String, String[]> parameterMap = request.getParameterMap();
        log.info("【前置通知 请求参数】：{}，", JSONUtil.toJsonStr(parameterMap));
        Long start = System.currentTimeMillis();
    }

    /**
     * 环绕通知
     *
     * @param point {@link ProceedingJoinPoint}
     * @return 原方法返回值
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) {
        Object result;
        long beginTime = System.currentTimeMillis();
        try {
            // 执行方法
            result = point.proceed();
            log.info("【环绕通知 返回值】：{}", JSONUtil.toJsonStr(result));
        } catch (Throwable e) {
            if (!(e instanceof BaseBusinessException)) {
                e.printStackTrace();
                Response errorBody = new Response();
                errorBody.setStatus(Response.Status.FAILED);
                errorBody.setErrorCode("SYS.500");
                errorBody.setErrorMessage(e.toString());
                errorBody.setExtMessage(FormatTool.buildJson(e));
                return errorBody;
            } else {
                BaseBusinessException exception = (BaseBusinessException) e;
                IError error = exception.getError();
                Response errorBody = new Response();
                errorBody.setStatus(Response.Status.FAILED);
                errorBody.setErrorCode(error.getErrorCode());
                errorBody.setErrorMessage(error.getErrorMessage());
                errorBody.setExtMessage(FormatTool.buildJson(e));
                return errorBody;
            }
        }

        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;

        // 保存操作日志
        saveLog(point, time, result);
        return result;
    }

    /**
     * 后置操作
     */
    @AfterReturning("pointcut()")
    public void afterReturning() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

        Long end = System.currentTimeMillis();
        log.info("【后置操作 请求耗时】：{}毫秒", end);

        String header = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(header);
        log.info("【后置操作 浏览器类型】：{}，【操作系统】：{}，【原始User-Agent】：{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header);
    }

    /**
     * 异常通知：在方法出现异常时进行通知,可以访问到异常对象，且可以指定在出现特定异常时在执行通知。
     *
     * @param joinPoint {@link JoinPoint}
     * @param e         异常信息
     */
    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println(String.format("afterThrowing方法名称：%s，异常参数：%s", methodName, e));
    }


    private void saveLog(ProceedingJoinPoint joinPoint, long time, Object result) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> aClass = joinPoint.getTarget().getClass();
        Api annotation = aClass.getAnnotation(Api.class);
        if (null == annotation) {
            return;
        }
        // 模块
        String moduleName = annotation.value();
        Method method = signature.getMethod();
        ApiOperation logAnnotation = method.getAnnotation(ApiOperation.class);
        // 操作
        String optionName = logAnnotation.value();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        // 请求的方法名
        String optionMethodName = className + "." + methodName + "()";
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 从获取RequestAttributes中获取HttpServletResponse的信息
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName() + " " + cookie.getValue());
            }
        }
        int status = response.getStatus();

        StringBuffer sb = new StringBuffer();
        sb.append("\n【模块】：").append(moduleName);
        sb.append("\n【操作】：").append(optionName);
        sb.append("\n【请求 URL】：").append(request.getRequestURL());
        sb.append("\n【请求 IP】：").append(request.getRemoteHost());
        sb.append("\n【请求类名】：").append(joinPoint.getSignature().getDeclaringTypeName());
        sb.append("\n【请求方法名】：").append(joinPoint.getSignature().getName());
        sb.append("\n【HTTP_METHOD】：").append(request.getMethod());
        sb.append("\n【URL参数】：").append(JsonTool.getObjToJson(request.getParameterMap()));
        sb.append("\n【body参数】：").append(JsonTool.getObjToJson(joinPoint.getArgs()));
        sb.append("\n【请求者的IP】：").append(request.getRemoteHost());
        sb.append("\n【http状态码】：").append(status);
        sb.append("\n【执行结果】：").append(JsonTool.getObjToJson(result));
        sb.append("\n【http状态码】：").append(status);
        sb.append("\n【执行时间】：").append(time).append("毫秒");
        System.out.println(sb);

        // 下面两个数组中，参数值和参数名的个数和位置是一一对应的。
        Object[] args3 = joinPoint.getArgs(); // 参数值
        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); // 参数名
        System.out.println(Arrays.toString(args3));

    }


}
